function radian(degree) {
	// angle to radian
	return degree * (Math.PI / 180);
}

function angleFrom(point1, point2) {
	// angle from two points
	// return 0 - 360
	let ang;
	if (point1.x == point2.x && point1.y == point2.y) {
		ang = 0;
	} else {
		const theX = point2.x - point1.x;
		const theY = -(point2.y - point1.y);
		ang = Math.atan(theY / theX) / (Math.PI / 180);
		if (theX < 0) {
			ang += 180;
		}
		if (theX >= 0 && theY < 0) {
			ang += 360;
		}
		ang = 360 - ang;
	}
	return ang;
}

function clamp(val, minValue, maxValue) {
	if (Array.isArray(val)) {
		return val.map((v) => Math.max(minValue, Math.min(maxValue, v)));
	} else if (typeof val === 'number') {
		return Math.max(minValue, Math.min(maxValue, val));
	}
}

function sign(x) {
	// sign(3);     //  1
	// sign(-3);    // -1
	// sign("-3");  // -1
	// sign(0);     //  0
	// sign(-0);    // -0
	// sign("foo"); // NaN
	// sign();      // NaN
	x = +x;
	if (x === 0 || isNaN(x)) return x;
	return x > 0 ? 1 : -1;
}

function distance2(point1, point2) {
	const dx = point1.x - point2.x;
	const dy = point1.y - point2.y;
	const dist = Math.sqrt(dx * dx + dy * dy);
	return dist;
}

function distance3(point1, point2) {
	const dx = point1.x - point2.x;
	const dy = point1.y - point2.y;
	const dz = point1.z - point2.z;
	const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
	return dist;
}

function fract(val) {
	return val - Math.floor(val);
}

function interpolate(point1, point2, val) {
	return {
		x: val * point1.x + (1 - val) * point2.x,
		y: val * point1.y + (1 - val) * point2.y
	};
}

function polar(len, angle) {
	return {
		x: len * Math.cos(angle) + 0.00001,
		y: len * Math.sin(angle) + 0.00001
	};
}

function normalize2(point, thickness) {
	let x = point.x;
	let y = point.y;
	const l = Math.sqrt(point.x * point.x + point.y * point.y);
	if (l > 0) {
		const f = thickness / l;
		x *= f;
		y *= f;
	}
	return { x, y };
}

function normalize3(point, thickness) {
	let x = point.x;
	let y = point.y;
	let z = point.z;
	const l = Math.sqrt(point.x * point.x + point.y * point.y + point.z * point.z);
	if (l > 0) {
		const f = thickness / l;
		x *= f;
		y *= f;
		z *= f;
	}
	return { x, y, z };
}

function diagonal(w, h) {
	return Math.sqrt(w * w + h * h);
}

function smoothstep(minValue, maxValue, val) {
	if (val <= minValue) return 0;
	if (val >= maxValue) return 1;
	val = (val - minValue) / (maxValue - minValue);
	return val * val * (3 - val * 2);
}

function smootherstep(minValue, maxValue, val) {
	if (val <= minValue) return 0;
	if (val >= maxValue) return 1;
	val = (val - minValue) / (maxValue - minValue);
	return val * val * val * (val * (val * 6 - 15) + 10);
}

function mod(val, maxValue) {
	return ((val % maxValue) + maxValue) % maxValue;
}

function lerp(minValue, maxValue, val) {
	// val 0-1, value between minValue - maxValue
	return val <= 0 ? minValue : val >= 1 ? maxValue : minValue + (maxValue - minValue) * val;
}

function lerpAngle(a, b, val) {
	let delta = mod(b - a, Math.PI * 2);
	if (delta > Math.PI) delta -= Math.PI * 2;
	return a + delta * clamp(val, 0, 1);
}

function parabola(val, expoValue) {
	return (4 * val * (1 - val)) ** expoValue;
}

function map(val, a1, a2, b1, b2) {
	// transfer ratio from range <a1, a2> to range <b1, b2>
	// e.g 0-100  to  0.2-0.7
	// e.g val = map( mouseX, -w, w, 2, 0 ); get 2 - 0;
	const res = b1 + ((val - a1) * (b2 - b1)) / (a2 - a1);
	const max = b1 > b2 ? b1 : b2;
	const min = b1 > b2 ? b2 : b1;
	return Math.max(min, Math.min(res, max));
}

function sientificToDecimal(val) {
	// Sientific notation to Decimal
	// console.log( scientificToDecimal(1.79769313e+308) )
	// console.log( scientificToDecimal(1.19209290e-7) )
	// console.log( scientificToDecimal(6e-7) )
	// console.log( (0.0000006).toExponential() )
	if (/\d+\.?\d*e[\+\-]*\d+/i.test(val)) {
		const zero = '0';

		const // split into coeff and exponent
			parts = String(val).toLowerCase().split('e');

		const // store the exponential part
			e = parts.pop();

		let // get the number of zeros
			l = Math.abs(e);

		const signed = e / l;
		const coeff_array = parts[0].split('.');

		if (signed === -1) {
			val = `${zero}.${new Array(l).join(zero)}${coeff_array.join('')}`;
		} else {
			const dec = coeff_array[1];
			if (dec) l = l - dec.length;
			val = coeff_array.join('') + new Array(l + 1).join(zero);
		}
	}
	return val;
}

function rand(minValue, maxValue) {
	return Math.min(maxValue, minValue + Math.random() * (maxValue * 1.123 - minValue));
}

function randSign(minValue, maxValue) {
	let signed = Math.random() - 0.5;
	if (signed == 0) signed = 1;
	return rand(minValue, maxValue) * sign(signed);
}

function randInt(minValue, maxValue) {
	return ~~Math.min(minValue + Math.random() * (maxValue + 2 - minValue), maxValue);
}

function hash(val1, val2) {
	// hash between 0-1
	return fract(Math.sin(val1 * 12.9898 + val2 * 4.1414) * 43758.5453);
}

function isOdd(num) {
	return num & 1;
}

function isEven(num) {
	return !(num & 1);
}

function isPowerOfTwo(val) {
	return (val & (val - 1)) === 0 && val !== 0;
}

function nearestPowerOfTwo(val) {
	return 2 ** Math.round(Math.log(val) / Math.LN2);
}

export {
	radian,
	angleFrom,
	clamp,
	sign,
	distance2,
	distance3,
	fract,
	interpolate,
	polar,
	normalize2,
	normalize3,
	diagonal,
	smoothstep,
	smootherstep,
	mod,
	lerp,
	lerpAngle,
	parabola,
	map,
	sientificToDecimal,
	rand,
	randSign,
	randInt,
	hash,
	isOdd,
	isEven,
	isPowerOfTwo,
	nearestPowerOfTwo
};
